package com.cy.pj.common.aspect;
import java.lang.reflect.Method;
import java.util.Date;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import com.cy.pj.common.annotation.RequiredLog;
import com.cy.pj.common.util.IPUtils;
import com.cy.pj.common.util.ShiroUtils;
import com.cy.pj.sys.pojo.SysLog;
import com.cy.pj.sys.service.SysLogService;
import com.fasterxml.jackson.databind.ObjectMapper;

import lombok.extern.slf4j.Slf4j;
/**
 * @Aspect 注解描述的类为spring容器中的一个切面对象类型(此类型中封装切入点与通知方法)
 * 1)切入点:(要执行拓展业务的方法的集合)
 * 2)通知方法:封装了在切入点方法上要执行的拓展业务方法.
 */
//@Order(1)
@Slf4j
@Aspect
@Component
public class SysLogAspect {
   //private static final Logger log=LoggerFactory.getLogger(SysLogAspect.class);
	/**
	 * @Pointcut 注解用于描述切入点(在哪些点上执行拓展业务)
	 * bean(bean对象名字):为一种切入点表达式(这个表达式中定义了哪个或哪些bean对象的方法要进行功能扩展).
	 * 	例如,bean(sysUserServiceImpl)表达式表示名字为sysUserServiceImpl的bean对象中所有方法的集合为切入点,
	   *   也就是说这个sysUserServiceImpl对象中的任意方法执行时都要进行功能扩展.
	 */
	//@Pointcut("bean(sysUserServiceImpl)")
	@Pointcut("@annotation(com.cy.pj.common.annotation.RequiredLog)")
	public void doLogPointCut() {}//此方法内部不需要写具体实现(方法的方法名也是任意)
	/**
	 * Around注解描述的方法为一个通知方法(服务增益方法),此方法内部可以做服务增益(拓展业务),@Around注解
	 * 	内部要指定切入点表达式,在此切入点表达式对应的切入点方法上做功能扩展.
	 * @param jp 表示连接点,连接点是动态确定的,用于封装正在执行的切入点方法(目标方法)信息.
	 * @return 目标方法的执行结果
	 * @throws Throwable 通知方法执行过程中出现的异常
	 */
	@Around("doLogPointCut()")
	public Object doLogAround(ProceedingJoinPoint jp)throws Throwable {
		//1.记录方法开始执行时间
		try {
			long t1=System.currentTimeMillis();
			log.info("start:{}",t1);
			//2.执行目标方法
			Object result=jp.proceed();//最终(中间还可以调用本类其它通知或其它切面的通知)会调用目标方法
			//3.记录方法结束执行时间
			long t2=System.currentTimeMillis();
			log.info("after:{}",t2);
			String targetClassMethod=getTargetClassMethod(jp);
			log.info("{}目标方法执行耗时:{}",targetClassMethod,(t2-t1));
			//4.将正常行为日志信息写入到数据库
			 saveUserLog(jp,(t2-t1));//new Thread(){}.start()
			//5.返回目标方法执行结果
			return result;//目标方法的返回结果
		}catch(Throwable e) {
			log.error("目标方法执行时出现了异常:{}",e.getMessage());
			throw e;
		}
	}
	@Autowired
	private SysLogService sysLogService;
	/**记录用户行为的正常信息*/
	private void saveUserLog(ProceedingJoinPoint jp,long time)throws Exception {
		//1.获取用户行为日志
		String ip=IPUtils.getIpAddr();
		String username=ShiroUtils.getUsername();//将来是登录用户的用户名
		Date createdTime=new Date();
		//获取操作名
		String operation=getOperation(jp);
		String method=getTargetClassMethod(jp);
		//String params=Arrays.toString(jp.getArgs());//普通格式字符串
		String params=new ObjectMapper().writeValueAsString(jp.getArgs());//json格式的参数
		//2.封装用户行为日志(在内容中通过对象去实现)
		SysLog log=new SysLog();
		log.setIp(ip);
		log.setUsername(username);
		log.setCreatedTime(createdTime);
		log.setOperation(operation);
		log.setMethod(method);
    	log.setParams(params);
		log.setTime(time);
		//3.将用户行为日志持久化(将内存中对象信息写入到数据库)
		sysLogService.saveObject(log);
		
//		new Thread() {//在JVM内容创建一个线程对象默认会分配大概1M内存,run方法运行结束,线程对象生命周期结束,线程对象占用的内存也要释放.
//			public void run() {
//				sysLogService.saveObject(log);
//			};
//		}.start();
		
	}
	/**获取目标方法上@ReqiredLog注解中定义的operation名字*/
	private String getOperation(ProceedingJoinPoint jp)throws Exception {
		//1.获取目标对象类型
		Class<?> targetCls=jp.getTarget().getClass();
		//2.获取目标方法对象
		//2.1获取方法签名信息
		MethodSignature ms=(MethodSignature)jp.getSignature();
		//2.2获取方法对象
		//假如底层配置为jdk代理,则method指向接口中的抽象方法对象.
		//假如底层配置为CGLIB代理,则这个method指向具体目标对象中的方法对象
	    //Method method=ms.getMethod();
	    //假如希望无论是jdk代理还是cglib代理,我们让method变量指向的都是目标对象中的方法对象,那如何实现?
		Method method=targetCls.getDeclaredMethod(ms.getName(), ms.getParameterTypes());
		//3.获取方法上的reqiredLog注解对象
		RequiredLog requiredLog=method.getAnnotation(RequiredLog.class);
		//4.获取注解中的operation的值.
		if(requiredLog==null)return "operation";
		return requiredLog.operation();
	}
	/**获取目标方法的全限定名(目标类全名+方法名)*/
	private String getTargetClassMethod(ProceedingJoinPoint jp) {
		//1.获取目标对象类型
		Class<?> targetCls=jp.getTarget().getClass();
		//2.获取目标对象类型的类全名
		String targetClsName=targetCls.getName();
		//3.获取目标方法名
		//3.1获取方法签名(方法签名对象中封装了方法相关信息)
		//Signature s=jp.getSignature();
		MethodSignature ms=(MethodSignature)jp.getSignature();
		//3.2基于方法签名获取方法名
		//String methodName=s.getName();
		String methodName=ms.getName();
		//4.构建方法的全限定名并返回
		return targetClsName+"."+methodName;
	}
}







